using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;
using System.Globalization;
using ResourceBlender_Express.Validators;
using System.ComponentModel;
using System.Data.Common;
using System.Data;
using System.Windows.Controls.Primitives;

namespace ResourceBlender_Express {
    /// <summary>
    /// A window for adding or editing an element and the resource it uses.
    /// </summary>
    public partial class WinAddResource : Window {
        private RBResource resource;

        /// <summary>
        /// Gets or sets the resource being edited.
        /// </summary>
        /// <value>The resource.</value>
        public RBResource Resource {
            get { return resource; }
            set {
                resource = value;
                DataContext = resource;
            }
        }

        public WinAddResource(int resourceId) {
            InitializeComponent();
            resource = new RBResource();
            if(resourceId > 0) { // could be modified to allow creation of new resources
                resource.ID = resourceId;
                // untested: not sure how this will work since there are no languages
            }
            LoadResource(resourceId);
            PopulateLanguages();
        }

        public WinAddResource(RBResource resource) {
            InitializeComponent();
            this.Resource = resource;
            PopulateLanguages();
            LoadTranslatorComments();
        }

        /// <summary>
        /// Sets the text on the status message (null hides the statusbar and label).
        /// </summary>
        /// <param name="message">The message.</param>
        private void SetStatus(string message) {
            if(message == null) {
                prgStatus.Visibility = Visibility.Hidden;
                lblStatus.Visibility = Visibility.Hidden;
            } else {
                prgStatus.Visibility = Visibility.Visible;
                lblStatus.Visibility = Visibility.Visible;
                lblStatus.Content = message;
            }
        }

        /// <summary>
        /// Populates the textboxes for each language used by the application.
        /// </summary>
        private void PopulateLanguages() {
            stkTranslations.Children.RemoveRange(0, stkTranslations.Children.Count);
            IDictionary<string, RBTranslation> translations = Resource.Translations;
            foreach(KeyValuePair<string, RBTranslation> translation in translations) { // <code, translation>
                string englishName;
                try {
                    englishName = CultureInfo.GetCultureInfo(translation.Key).EnglishName;
                } catch {
                    englishName = translation.Key;
                }
                TranslationInput translationInput = new TranslationInput(englishName);
                Binding bndTranslation = new Binding(string.Format("Translations[{0}]", translation.Key));
                bndTranslation.Mode = BindingMode.TwoWay;
                bndTranslation.Source = Resource;
                translationInput.SetBinding(TranslationInput.TranslationProperty, bndTranslation);
                translationInput.Margin = new Thickness(0, 2, 0, 2);
                stkTranslations.Children.Add(translationInput);
                translationInput.Expand();
            }
        }

        /// <summary>
        /// Loads the translator comments for the selected element.
        /// </summary>
        private void LoadTranslatorComments() {
            BackgroundWorker worker = new BackgroundWorker();
            SetStatus("Loading translator comments...");
            worker.DoWork += (s, dwe) => {
                Database db = null;
                try {
                    db = new Database();
                    db.OpenConnection();
                    using(Database.Command cmd = db.CreateCommand()) {
                        cmd.CommandText = string.Format("SELECT language, translatorcomment FROM {0}resources WHERE resourceid = {1}{2}", db.TablePrefix, Resource.ID, db.GenerateInClause("language", resource.Translations.Keys.ToArray(), true));
                        DbDataReader reader = cmd.ExecuteReader();
                        while(reader.Read()) {
                            Resource.Translations[reader["language"].ToString()].Comment = (reader["translatorcomment"] ?? string.Empty).ToString();
                        }
                        reader.Close();
                    }
                } catch(Exception ex) {
                    dwe.Result = ex.Message;
                } finally {
                    if(db != null) {
                        try {
                            db.CloseConnection();
                        } catch { }
                    }
                }
            };
            worker.RunWorkerCompleted += (s, rwe) => {
                SetStatus(null);
                if(rwe.Result != null) {
                    DialogBox.Show(rwe.Result.ToString(), "Error Loading Translator Comments", DialogBox.DialogBoxType.OK, DialogBox.DialogBoxIcon.Error);
                }
            };
            worker.RunWorkerAsync();
        }

        /// <summary>
        /// Loads a new resource.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Controls.SelectionChangedEventArgs"/> instance containing the event data.</param>
        private void LoadResource(int resourceId) {
            BackgroundWorker worker = new BackgroundWorker();
            WinProgress progress = new WinProgress("Loading resource...", true);
            progress.Owner = this;
            worker.DoWork += (s, dwe) => {
                Database db = null;
                try {
                    db = new Database();
                    db.OpenConnection();
                    using(Database.Command cmd = db.CreateCommand()) {
                        int resourceID = int.Parse(dwe.Argument.ToString());
                        ObservableSortedDictionary<string, string> availableLanguages = new ObservableSortedDictionary<string, string>(new Comparers.DictionaryValueComparer(), new Comparers.StringEqualityComparer());
                        cmd.CommandText = string.Format("SELECT DISTINCT language FROM {0}resources", db.TablePrefix);
                        cmd.AddParameterWithValue("@tID", resourceID);
                        DbDataReader reader = cmd.ExecuteReader();
                        while(reader.Read()) {
                            string code = reader[0].ToString();
                            if(code.Trim().Length == 0)
                                continue;
                            availableLanguages.Add(code, code); // english name not needed
                        }
                        reader.Close();
                        string crossTabColumns = db.GetCrosstabLanguageSql(availableLanguages);

                        cmd.CommandText = string.Format("SELECT {0} FROM {1}resources WHERE resourceid = @tID", crossTabColumns, db.TablePrefix);
                        RBResource newResource = new RBResource(resourceID, string.Empty);
                        reader = cmd.ExecuteReader();
                        if(reader.Read()) {
                            for(int i = 0; i < reader.FieldCount; i++) {
                                string languageCode = reader.GetName(i);
                                newResource.Translations.Add(languageCode, new RBTranslation(languageCode, (reader[i] ?? string.Empty).ToString(), ""));
                            }
                        }
                        reader.Close();

                        // fill the translators' comment boxes by changing the field name in the query
                        cmd.CommandText = cmd.CommandText.Replace("translation", "translatorcomment");
                        reader = cmd.ExecuteReader();
                        if(reader.Read()) {
                            for(int i = 0; i < reader.FieldCount; i++) {
                                newResource.Translations[reader.GetName(i)].Comment = (reader[i] ?? string.Empty).ToString();
                            }
                        }
                        reader.Close();

                        // fill the overall resource comment
                        cmd.CommandText = "SELECT comment FROM " + db.TablePrefix + "resourcecomments WHERE resourceid = @tID";
                        newResource.Comment = (cmd.ExecuteScalar() ?? string.Empty).ToString();
                        dwe.Result = newResource;
                    }
                } catch(Exception ex) {
                    dwe.Result = "Error changing displayed resource: " + ex.Message;
                } finally {
                    if(db != null) {
                        try {
                            db.CloseConnection();
                        } catch { }
                    }
                }
            };
            worker.RunWorkerCompleted += (s, rwe) => {
                progress.Close();
                if(rwe.Result is RBResource) {
                    Resource = rwe.Result as RBResource;
                    PopulateLanguages();
                } else {
                    DialogBox.Show(rwe.Result.ToString(), "Database Error", DialogBox.DialogBoxType.OK, DialogBox.DialogBoxIcon.Error);
                }
            };
            progress.Show();
            worker.RunWorkerAsync(resourceId);
        }

        /// <summary>
        /// Validates the user input, saves/updates the element and it's resource then closes the dialog.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void Save(object sender, ExecutedRoutedEventArgs e) {
            BackgroundWorker worker = new BackgroundWorker();
            worker.DoWork += (s, dwe) => {
                Database db = null;
                List<string> errors = new List<string>();
                try {
                    db = new Database();
                    db.OpenConnection();
                    using(Database.Command cmd = db.CreateCommand()) {
                        int resourceID = resource.ID;
                        if(resourceID == -1) {
                            cmd.CommandText = "SELECT MAX(resourceid) FROM " + db.TablePrefix + "resources";
                            int maxId = 0;
                            using(DbDataReader maxIdReader = cmd.ExecuteReader(CommandBehavior.SingleRow)) {
                                if(maxIdReader.HasRows) {
                                    maxIdReader.Read();
                                    int.TryParse(maxIdReader[0].ToString(), out maxId);
                                    maxId++;
                                }
                                maxIdReader.Close();
                            }
                            resourceID = maxId;
                        }

                        cmd.Parameters.Clear();
                        switch(db.InstanceType) {
                            case Database.DatabaseType.MySQL:
                                cmd.CommandText = string.Format("INSERT INTO {0}resources (resourceid, language, translation, translatorcomment) VALUES (@tID, @lang, @trans, @comment) ON DUPLICATE KEY UPDATE translation = @trans, translatorcomment = @comment", db.TablePrefix);
                                break;
                            case Database.DatabaseType.SQLServer:
                                cmd.CommandText = string.Format("UPDATE {0}resources SET translation = @trans, translatorcomment = @comment WHERE resourceid = @tID AND language = @lang; IF @@ROWCOUNT = 0 INSERT INTO {0}resources (resourceid, language, translation, translatorcomment) VALUES (@tID, @lang, @trans, @comment)", db.TablePrefix);
                                break;
                            case Database.DatabaseType.SQLite:
                                cmd.CommandText = string.Format("INSERT OR REPLACE INTO {0}resources (resourceid, language, translation, translatorcomment) VALUES (@tID, @lang, @trans, @comment)", db.TablePrefix);
                                break;
                        }
                        cmd.AddParameterWithValue("@tID", resourceID);
                        cmd.AddParameterWithValue("@trans", "");
                        cmd.AddParameterWithValue("@lang", "");
                        cmd.AddParameterWithValue("@comment", "");
                        foreach(KeyValuePair<string, RBTranslation> translation in resource.Translations) { // <code, translation>
                            if(!string.IsNullOrEmpty(translation.Value.Translation) || !string.IsNullOrEmpty(translation.Value.Comment)) {
                                cmd.Parameters["@trans"].Value = translation.Value.Translation;
                                cmd.Parameters["@lang"].Value = translation.Key;
                                if(string.IsNullOrEmpty(translation.Value.Comment)) {
                                    cmd.Parameters["@comment"].Value = DBNull.Value;
                                } else {
                                    cmd.Parameters["@comment"].Value = translation.Value.Comment;
                                }
                                try {
                                    cmd.ExecuteNonQuery();
                                } catch(Exception ex) {
                                    errors.Add(string.Format("Error updating translation for {0}: {1}", translation.Key, ex.Message));
                                }
                            }
                        }
                        if(!string.IsNullOrEmpty(Resource.Comment)) {
                            cmd.Parameters.Clear();
                            switch(db.InstanceType) {
                                case Database.DatabaseType.MySQL:
                                    cmd.CommandText = string.Format("INSERT INTO {0}resourcecomments (resourceid, comment) VALUES (@tID, @comment) ON DUPLICATE KEY UPDATE comment = @comment", db.TablePrefix);
                                    break;
                                case Database.DatabaseType.SQLServer:
                                    cmd.CommandText = string.Format("UPDATE {0}resourcecomments SET comment = @comment WHERE resourceid = @tID; IF @@ROWCOUNT = 0 INSERT INTO {0}resourcecomments (resourceid, comment) VALUES (@tID, @comment)", db.TablePrefix);
                                    break;
                                case Database.DatabaseType.SQLite:
                                    cmd.CommandText = string.Format("INSERT OR REPLACE INTO {0}resourcecomments (resourceid, comment) VALUES (@tID, @comment)", db.TablePrefix);
                                    break;
                            }
                            cmd.AddParameterWithValue("@tID", resourceID);
                            cmd.AddParameterWithValue("@comment", Resource.Comment);
                            cmd.ExecuteNonQuery();
                        }
                        cmd.Parameters.Clear();
                    }
                } catch(Exception ex) {
                    errors.Add("Fatal error: " + ex.Message);
                } finally {
                    if(db != null) {
                        try {
                            db.CloseConnection();
                        } catch { }
                    }
                }
                if(errors.Count > 0) {
                    dwe.Result = Utils.Implode(errors, Environment.NewLine, false);
                }
            };
            worker.RunWorkerCompleted += (s, rwe) => {
                SetStatus(null);
                if(rwe.Result == null) {
                    Close();
                } else {
                    DialogBox.ShowAlert(this, rwe.Result.ToString(), "Database Error");
                }
            };
            SetStatus("Saving resource...");
            worker.RunWorkerAsync();
        }

        /// <summary>
        /// Closes the dialog without saving.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void CloseWindow(object sender, ExecutedRoutedEventArgs e) {
            Close();
        }

        /// <summary>
        /// Shows the larger editor.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void ShowFullEditor(object sender, ExecutedRoutedEventArgs e) {
            TextBox source = e.OriginalSource as TextBox;
            if(source == null) {
                return;
            }
            WinFullEditor editor = new WinFullEditor("Editor", source.Text);
            editor.Owner = this;
            editor.ShowDialog();
            if(editor.DialogResult == true) {
                source.Text = editor.Text;
            }
        }

        /// <summary>
        /// Shows a dialog to find resources which use a similar translation to the user's input
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void FindSimilar(object sender, ExecutedRoutedEventArgs e) {
            TextBox txtSource = e.OriginalSource as TextBox;
            WinFindResource dlg = new WinFindResource(new RBTranslation(e.Parameter.ToString(), txtSource.Text, ""), "");
            dlg.Owner = this;
            dlg.ShowDialog();
        }

        /// <summary>
        /// Automatically translates from the current UI language using Google Translate.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void AutoTranslate(object sender, ExecutedRoutedEventArgs e) {
            string uiCulture = CultureInfo.CurrentUICulture.Name;
            string englishName;
            try {
                englishName = CultureInfo.GetCultureInfo(uiCulture).EnglishName;
            } catch {
                englishName = uiCulture;
            }
            if(!resource.Translations.ContainsKey(uiCulture)) {
                DialogBox.ShowAlert(this, string.Format("{0} translation is not present", englishName), "Not Supported");
                //todo: make a dialog to choose from available google languages.
                // fill the dialog with cultures both supported by Google and present in the resource.
                return;
            }
            string googleSourceCode = GoogleTranslator.DotNetCode2GoogleCode(uiCulture),
                   destinationCode = e.Parameter.ToString(),
                   googleDestinationCode = GoogleTranslator.DotNetCode2GoogleCode(destinationCode);
            string textToTranslate = Resource.Translations[uiCulture].Translation.Trim();
            if(googleSourceCode.Length == 0 || googleDestinationCode.Length == 0) {
                DialogBox.ShowAlert(this, "Translation between the specified languages is not supported", "Not Supported");
            } else if(textToTranslate.Length == 0) {
                return;
            } else if(textToTranslate.Length > 4800) {
                DialogBox.ShowAlert(this, "Only strings up to 4800 characters in length can be translated", "Not Supported");
            } else {
                BackgroundWorker worker = new BackgroundWorker();
                worker.DoWork += (s, dwe) => {
                    try {
                        dwe.Result = GoogleTranslator.Translate(textToTranslate, googleSourceCode, googleDestinationCode);
                    } catch(Exception ex) {
                        dwe.Result = ex.Message;
                    }
                };
                worker.RunWorkerCompleted += (s, rwe) => {
                    if(rwe.Result is GoogleTranslator.TranslationResponse) {
                        GoogleTranslator.TranslationResponse response = rwe.Result as GoogleTranslator.TranslationResponse;
                        if(response.Status == 200) {
                            Resource.Translations[destinationCode].Translation = response.Data.TranslatedText;
                        } else {
                            DialogBox.ShowAlert(this,
                                string.Format("Error {0}: {1}",
                                    response.Status,
                                    response.Status == 400 ? string.Format("{0} translation not supported by {1}\n\n{2}", englishName, "Google", response.Details) : response.Details),
                            "Error translating resource");
                        }
                    } else {
                        DialogBox.ShowAlert(this, rwe.Result.ToString(), "Error translating resource");
                    }
                };
                worker.RunWorkerAsync();
            }
        }
    }
}